package org.zoomdev.zoom.common.caster;

import org.zoomdev.zoom.common.utils.Classes;

import java.io.File;
import java.io.InputStream;
import java.io.Reader;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.sql.Blob;
import java.sql.Clob;
import java.sql.NClob;
import java.sql.Time;
import java.sql.Timestamp;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;


/**
 * This tool box can cast a value into another class. Caster.to( value ,
 * Integer.class)
 * <p>
 * <p>
 * 目前支持基本数据
 *
 * @author Administrator
 */
public final class Caster {
    static final EqValueCaster EQ_VALUE_CASTER = new EqValueCaster();
    private static final ConcurrentHashMap<TypeKey, ValueCaster> map = new ConcurrentHashMap<>(256);
    private static final ConcurrentHashMap<Class<?>, ValueCaster> anyCasters = new ConcurrentHashMap<>(32);

    public static volatile CasterProvider[] providers;
    //日期格式   ISO_DATE

    //毫秒

    static EmptyValueCaster EMPTY = new EmptyValueCaster();

    static {


        register(Object.class, String.class, Object2String.CASTER);


        register(Double.class, boolean.class, new BasicTypes.Double2Boolean());
        register(Float.class, boolean.class, new BasicTypes.Float2Boolean());

        //数字到其他
        register(Number.class, double.class, new BasicTypes.Number2Double());
        register(Number.class, int.class, new BasicTypes.Number2Integer());
        register(Number.class, boolean.class, new BasicTypes.Number2Boolean());
        register(Number.class, float.class, new BasicTypes.Number2Float());
        register(Number.class, short.class, new BasicTypes.Number2Short());
        register(Number.class, char.class, new BasicTypes.Number2Char());
        register(Number.class, byte.class, new BasicTypes.Number2Byte());
        register(Number.class, long.class, new BasicTypes.Number2Long());

        //String 到 基本类型
        register(String.class, char.class, new BasicTypes.String2Char());
        register(String.class, int.class, new BasicTypes.String2Integer());
        register(String.class, boolean.class, new BasicTypes.String2Boolean());
        register(String.class, long.class, new BasicTypes.String2Long());
        register(String.class, short.class, new BasicTypes.String2Short());
        register(String.class, byte.class, new BasicTypes.String2Byte());
        register(String.class, double.class, new BasicTypes.String2Double());
        register(String.class, float.class, new BasicTypes.String2Float());

        //boolean
        register(boolean.class, int.class, new BasicTypes.Boolean2Int());
        register(boolean.class, long.class, new BasicTypes.Boolean2Long());
        register(boolean.class, byte.class, new BasicTypes.Boolean2Byte());
        register(boolean.class, float.class, new BasicTypes.Boolean2Float());
        register(boolean.class, double.class, new BasicTypes.Boolean2Double());
        register(boolean.class, char.class, new BasicTypes.Boolean2Char());
        //database


        //date
        register(Date.class, java.sql.Date.class, new BasicTypes.Date2SqlDate());


        //register(java.sql.Date.class, java.util.Date.class, new SqlDate2Date());


        //CLASS
        register(String.class, Class.class, new BasicTypes.String2Class());

        //json
        //  register(String.class, Map.class, new Maps.String2Map());
        //  register(String.class, List.class, new BasicTypes.String2List());

        //这里可以看情况是否要覆盖默认情况
        register(InputStream.class, Map.class, new InputStream2Map());
        register(InputStream.class, List.class, new InputStream2List());
        register(Reader.class, Map.class, new Maps.Reader2Map());
        register(Reader.class, List.class, new Reader2List());
        register(List.class, String.class, new JsonObject2String());
        register(Set.class, String.class, new JsonObject2String());
        register(Collection.class, String.class, new JsonObject2String());
        register(Map.class, String.class, new JsonObject2String());


        // Database types
        register(Clob.class, String.class, new ClobAndBlob.Clob2String());
        register(NClob.class, String.class, new ClobAndBlob.Clob2String());
        register(Blob.class, String.class, new ClobAndBlob.Blob2String());
        register(Blob.class, byte[].class, new ClobAndBlob.Blob2ByteArray());


        //BigDecimal
        register(BigDecimal.class, boolean.class, new BasicTypes.BigDecimal2Boolean());
        register(Integer.class, BigDecimal.class, new BasicTypes.Integer2BigDecimal());
        register(Double.class, BigDecimal.class, new BasicTypes.Double2BigDecimal());
        register(Long.class, BigDecimal.class, new BasicTypes.Long2BigDecimal());
        register(Short.class, BigDecimal.class, new BasicTypes.Short2BigDecimal());
        register(Boolean.class, BigDecimal.class, new BasicTypes.Boolean2BigDecimal());
        register(String.class, BigDecimal.class, new BasicTypes.String2BigDecimal());

        //规则是 转成Integer表示的是秒， 而long和double表示的是毫秒，其他数字类型不接受
        register(Date.class, int.class, new BasicTypes.Date2Integer());
        register(Date.class, long.class, new BasicTypes.Date2Long());
        register(String.class, byte[].class, new BasicTypes.String2ByteArray());
        register(String.class, Date.class, BasicTypes.String2Date.STRING_2_DATE);
        register(File.class, byte[].class, new File2ByteArray());
        register(File.class, InputStream.class, new File2InputStream());
        register(Timestamp.class, Date.class, new BasicTypes.Timestamp2Date());
        register(long.class, Timestamp.class, new BasicTypes.Long2Timestamp());
        register(long.class, BigInteger.class, new BasicTypes.Long2BigInteger());
        register(String.class, BigInteger.class, new BasicTypes.String2BigInteger());
        register(BigInteger.class, long.class, new BasicTypes.BigInteger2Long());


        register(Date.class, String.class, new BasicTypes.Date2String());

        ///local date time
        register(String.class, LocalDate.class, new LocalDates.String2LocalDate());
        register(LocalDate.class, String.class, new LocalDates.LocalDate2String());
        register(String.class, LocalTime.class, new LocalDates.String2LocalTime());
        register(LocalTime.class, String.class, new LocalDates.LocalTime2String());
        register(String.class, LocalDateTime.class, new LocalDates.String2LocalDateTime());
        register(LocalDateTime.class, String.class, new LocalDates.LocalDateTime2String());

        register(LocalDateTime.class, Timestamp.class, new LocalDates.LocalDateTime2Timestamp());
        register(Timestamp.class, LocalDateTime.class, new LocalDates.Timestamp2LocalDateTime());
        register(LocalDateTime.class, Date.class, new LocalDates.LocalDateTime2Date());
        register(Date.class, LocalDateTime.class, new LocalDates.Date2LocalDateTime());
        register(LocalDate.class, java.sql.Date.class, new LocalDates.LocalDate2SqlDate());
        register(LocalTime.class, java.sql.Time.class, new LocalDates.LocalTime2SqlTime());

        //time
        register(Date.class, Time.class, new BasicTypes.Date2SqlTime());
        //timestamp
        register(Date.class, Timestamp.class, new BasicTypes.Date2Timestamp());
        register(Timestamp.class, String.class, new BasicTypes.Timestamp2String());
        register(String.class, Timestamp.class, new BasicTypes.String2Timestamp());

        register(String.class, UUID.class, new String2UUID());

        // Register providers
        registerCastProvider(new ClobAndBlob.Clob2AnyProvider());
        registerCastProvider(new Enums.FromEmuProvider());
        registerCastProvider(new Enums.ToEnumProvider());
        registerCastProvider(new Maps.Map2MapProvider());
        registerCastProvider(new Maps.Map2BeanProvider());
        registerCastProvider(new BasicTypes.String2BeanProvider());
        registerCastProvider(new Maps.Bean2MapProvider());
        registerCastProvider(new String2AnyProvider());


        registerCastProvider(new Bean2BeanProvider());



    }


    private Caster() {

    }

    /**
     * 包装一个首次非空的值之后缓存的caster
     * 一开始不知道源类型，但是知道目标类型，源类型在之后的程序中不会改变了。
     *
     * @param dest
     * @return
     */
    public static ValueCaster wrapFirstVisit(Type dest) {
        if (dest instanceof Class && ((Class) dest).isPrimitive()) {
            return new WrapPrimitiveValueCaster(wrapFirstVisit0(dest), (Class) dest);
        }
        return new CheckNullCaster(wrapFirstVisit0(dest));
    }

    /**
     * 只知道要转化成toType，不知道源类型，源类型可能会改变
     *
     * @param toType
     * @return
     */
    public static ValueCaster wrap(Class<?> toType) {
        if (toType == null) {
            throw new CasterException("必须提供一个目标类型");
        }

        ValueCaster caster = anyCasters.get(toType);
        if (caster == null) {
            caster = new AnyCaster(toType);
            anyCasters.put(toType, caster);
        }
        return caster;
    }

    /**
     * Get a wrapped ValueCaster that check null value and return default value when value is null.
     *
     * @param srcType
     * @param toType
     * @return
     */
    public static ValueCaster wrap(Class<?> srcType, Class<?> toType) {
        if (toType == null) {
            throw new CasterException("toType must not be null");
        }
        if (srcType == null) {
            return wrapFirstVisit(toType);
        }

        if (srcType == toType || toType.isAssignableFrom(srcType)) {
            return EMPTY;
        }

        if (toType.isPrimitive()) {
            //转化的类型指定类的包装类
            return new WrapPrimitiveValueCaster(get(srcType, getWrapClass(toType)), toType);
        }

        if (srcType.isPrimitive()) {
            return wrap(getWrapClass(srcType), toType);
        }


        return new CheckNullCaster(get(srcType, toType));
    }

    public static synchronized void registerCastProvider(CasterProvider provider) {
        if (providers == null) {
            providers = new CasterProvider[]{provider};
        } else {
            CasterProvider[] oldCasterProviders = providers;
            CasterProvider[] providers = new CasterProvider[Caster.providers.length + 1];
            System.arraycopy(Caster.providers, 0, providers, 0, Caster.providers.length);
            providers[oldCasterProviders.length] = provider;
            Caster.providers = providers;
        }
    }

    public static CasterProvider[] getProviders() {
        return providers;
    }

    public static void setCastProvider(int i, CasterProvider newProvider) {
        if (i < 0 || i >= providers.length) {
            throw new IndexOutOfBoundsException("Invalid provider index");
        }
        providers[i] = newProvider;
    }

    /**
     * T cast some class to another class, you must register the tow classes and
     * a caster .
     *
     * @param src
     * @param to
     * @param caster
     */
    public static void register(Class<?> src, Class<?> to, ValueCaster caster) {
        if (to == null || src == null || caster == null) {
            throw new NullPointerException();
        }

        if (to.isPrimitive()) {
            map.put(TypeKey.of(src, getWrapClass(to)), caster);
        }

        if (src.isPrimitive()) {
            map.put(TypeKey.of(getWrapClass(src), to), caster);
        }

        if (to.isPrimitive() && src.isPrimitive()) {
            map.put(TypeKey.of(getWrapClass(src), getWrapClass(to)), caster);
        }

        map.put(TypeKey.of(src, to), caster);
    }

    public static Class<?> getWrapClass(Class<?> type) {
        if (type == int.class) {
            return Integer.class;
        }
        if (type == boolean.class) {
            return Boolean.class;
        }
        if (type == double.class) {
            return Double.class;
        }
        if (type == float.class) {
            return Float.class;
        }
        if (type == long.class) {
            return Long.class;
        }
        if (type == short.class) {
            return Short.class;
        }
        if (type == byte.class) {
            return Byte.class;
        }
        if (type == char.class) {
            return Character.class;
        }
        throw new CasterException("impossible");
    }

    @SuppressWarnings("rawtypes")
    public static <T> T toType(Object src, Type targetType) {
        if (targetType == null) {
            //这里没有什么好办法，只能先返回null再说
            return null;
        }

        if (targetType instanceof ParameterizedType) {
            return toParameterizedType(src, (ParameterizedType) targetType);
        }

        if (targetType instanceof Class) {
            return (T) to(src, (Class) targetType);
        }


        return null;
    }

    @SuppressWarnings("unchecked")
    public static <T> T to(Object src, Class<T> targetClass) {
        if (src == null) {
            if (targetClass.isPrimitive()) {
                // default of primitive value
                return (T) getDefaultValue(targetClass);
            }
            return null;
        }

        if (targetClass == null) {

            //这里没有什么好办法，只能先返回null再说
            return null;
        }

        Class<?> srcType = src.getClass();
        if (srcType == targetClass || targetClass.isAssignableFrom(srcType)) {
            return (T) src;
        }
        TypeKey key = TypeKey.of(srcType, targetClass);
        ValueCaster caster = map.get(key);
        if (caster == null) {
            caster = get(srcType, targetClass);
            map.put(key, caster);
        }

        return (T) caster.to(src);
    }

    public static <V> V getPossible(Class<?> srcType, Class<?> toType, Map<TypeKey, V> map) {
        List<Class<?>> srcs = ClassExtract.extractClass(srcType);
        for (Class s : srcs) {
            TypeKey key = TypeKey.of(s, toType);
            V caster = map.get(key);
            if (caster == null) {
                continue;
            }

            return caster;
        }
        return null;
    }

    public static ValueCaster wrapType(Class<?> srcType, Type toType) {
        if (toType == null) {
            throw new NullPointerException("srcType and toType must not be null");
        }
        if (srcType == null) {
            //如果一开始并不知道要转什么
            return wrapFirstVisit(toType);
        }


        if (toType instanceof ParameterizedType) {
            return wrapParameterizedType(srcType, (ParameterizedType) toType);
        }

        if (toType instanceof Class) {
            if (srcType == toType) {
                return EQ_VALUE_CASTER;
            }
            return wrap(srcType, (Class<?>) toType);
        }


        throw new CasterException("Cannot cast " + srcType + " to " + toType);
    }

    public static boolean isNumber(Type toType) {
        return toType == long.class || toType == short.class || toType == char.class || toType == byte.class || toType == double.class || toType == float.class;
    }

    private static ValueCaster wrapFirstVisit0(Type dest) {
        return new WrapExceptionCaster(new FirstVisitValueCaster(dest));
    }


    static Object getDefaultValue(Class<?> targetClass) {

        if (targetClass == int.class) {
            return 0;
        }
        if (targetClass == boolean.class) {
            return false;
        }
        if (targetClass == double.class) {
            return (double) 0;
        }
        if (targetClass == float.class) {
            return (float) 0;
        }
        if (targetClass == long.class) {
            return 0L;
        }
        if (targetClass == short.class) {
            return (short) 0;
        }
        if (targetClass == byte.class) {
            return (byte) 0;
        }
        if (targetClass == char.class) {
            return (char) 0;
        }
        throw new CasterException("impossible");
    }

    /**
     * dest 是否 是 src的包装类
     * <p>
     * <p>
     * isWrapClass(int.class,Integer.class)==true
     * isWrapClass(Integer.class,int.class)==false
     *
     * @param src  如 int.class
     * @param dest 如 Integer.class
     * @return
     */
    private static boolean isWrapClass(Class<?> src, Class<?> dest) {
        try {

            return dest.getField("TYPE").get(null) == src;
        } catch (Exception e) {
            return false;
        }
    }

    /// 一定是Array类型
    private static ValueCaster getWithIterable(Class<?> toType) {
        return new ArrayAndCollections.Iterable2Array(toType.getComponentType());
    }

    private static ValueCaster getWithArrayParameterizedType(ParameterizedType type) {
        Class<?> rawType = Classes.getClass(type);
        if (!Collection.class.isAssignableFrom(rawType)) {
            throw new CasterException("Cannot cast array to " + type + " dest type must be Collection ");
        }

        Type[] types = type.getActualTypeArguments();
        if (types.length != 1) {
            throw new CasterException("Cannot cast array to " + type + " dest type must be Collection ");
        }

        return new ArrayAndCollections.Array2ParameterizedType(rawType, types[0]);
    }

    /// 一定是Array类型
    private static ValueCaster getWithArray(Class<?> toType) {
        Class<?> componentType = toType.getComponentType();
        return new ArrayAndCollections.Array2Array(componentType);
    }

    static ValueCaster get(Class<?> srcType, Class<?> toType) {
        if (srcType == toType) {
            return EQ_VALUE_CASTER;
        }
        /**
         * 如果是int 转 Integer 或者是Integer 转 int,则可以相互转化
         */
        if (isWrapClass(srcType, toType) || isWrapClass(toType, srcType)) {
            return EQ_VALUE_CASTER;
        }


        if (toType.isArray()) {
            if (Iterable.class.isAssignableFrom(srcType)) {
                return getWithIterable(toType);
            } else if (srcType.isArray()) {
                if (srcType.getComponentType() == toType.getComponentType()) {
                    return EQ_VALUE_CASTER;
                }
                return getWithArray(toType);
            }
        }


        ValueCaster caster = getPossible(srcType, toType, map);
        if (caster != null) {
            return caster;
        }

        final CasterProvider[] providers = Caster.providers;
        if (providers != null) {
            for (CasterProvider casterProvider : providers) {
                caster = casterProvider.getCaster(srcType, toType);
                if (caster != null) {
                    map.put(TypeKey.of(srcType, toType), caster);
                    return caster;
                }
            }
        }


        throw new CasterException(String.format("Cannot cast %s to %s ", srcType.getName(), toType.getName()));
    }

    /**
     * @param src
     * @param targetType
     * @return
     */
    private static <T> T toParameterizedType(Object src, ParameterizedType targetType) {
        assert (targetType != null);

        if (src == null) {
            return null;
        }

        ValueCaster caster = getWithType(src.getClass(), targetType);
        if (caster == null) {
            throw new CasterException(String.format("Cannot cast %s to %s", src, targetType));
        }
        return (T) caster.to(src);
    }

    private static ValueCaster getWithType(Class<?> srcType, ParameterizedType targetType) {
        // String key = srcType.getName() + "2" + targetType.toString();
        TypeKey key = TypeKey.of(srcType, targetType);
        ValueCaster caster = map.get(key);
        if (caster == null) {
            if (srcType.isArray()) {
                caster = getWithArrayParameterizedType(targetType);
            } else if (Iterable.class.isAssignableFrom(srcType)) {
                caster = getWithIterable(targetType);
            } else if (Map.class.isAssignableFrom(srcType)) {
                caster = getWithMap(targetType);
            } else {
                final CasterProvider[] providers = Caster.providers;
                if (providers != null) {
                    for (CasterProvider casterProvider : providers) {
                        caster = casterProvider.getCaster(srcType, targetType);
                        if (caster != null) {
                            break;
                        }
                    }
                }
            }
        }

        if (caster != null) {
            map.put(key, caster);
        }

        return caster;
    }

    private static ValueCaster wrapParameterizedType(Class<?> srcType, ParameterizedType toType) {
        if (srcType == null || toType == null) {
            throw new NullPointerException("srcType and toType must not be null");
        }

        ValueCaster caster = getWithType(srcType, toType);
        if (caster == null) {
            throw new CasterException("Cannot cast " + srcType + " to " + toType);
        }

        return new CheckNullCaster(caster);

    }

    private static ValueCaster getWithMap(ParameterizedType targetType) {
        Type rowType = targetType.getRawType();
        if (rowType instanceof Class) {
            if (!Map.class.isAssignableFrom((Class) rowType)) {
                throw new CasterException("Cannot cast Map to type:" + targetType + " targetType must be Map");
            }
            return new Maps.MapValueCaster(targetType);
        } else {
            throw new CasterException("Cannot cast Map to type:" + targetType + " targetType must be Map");
        }

    }

    private static ValueCaster getWithIterable(
            ParameterizedType targetType) {
        Type[] types = targetType.getActualTypeArguments();
        if (types.length > 1) {
            throw new CasterException("Cannot cast src:Iterable to type:" + targetType);
        }

        Type collectionType = targetType.getRawType();
        //如果还是一个泛型?
        if (collectionType instanceof ParameterizedType) {
            throw new CasterException("Cannot cast src:Iterable to type:" + targetType + " targetType must be some kind of Iterable");
        }

        Class<?> collectionClass = (Class<?>) collectionType;
        if (!Collection.class.isAssignableFrom(collectionClass)) {
            throw new CasterException("Cannot cast src:Iterable to type:" + targetType + " targetType must be some kind of Iterable");
        }

        return new ArrayAndCollections.Iterable2Collection((Class<? extends Collection>) collectionClass, types[0]);
    }

    static class String2UUID implements ValueCaster {

        @Override
        public Object to(Object src) {
            return UUID.fromString((String) src);
        }
    }
}
